#！ usr/bin/env python
### Python 函数

# 函数是组织好的，可重复使用的，用来实现单一，或相关联功能的代码段。
# 函数能提高应用的模块性，和代码的重复利用率。你已经知道Python提供了许多内建函数，比如print()。但你也可以自己创建函数，这被叫做用户自定义函数


### 定义一个函数

# 你可以定义一个由自己想要功能的函数，以下是简单的规则：
# 函数代码块以 def 关键词开头，后接函数标识符名称和圆括号 ()。
# 任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。
# 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
# 函数内容以冒号 : 起始，并且缩进。
# return [表达式] 结束函数，选择性地返回一个值给调用方，不带表达式的 return 相当于返回 None

# 实例格式：
# from re import S
# from this import s    # 会在终端引出 python 禅，不是报错，彩蛋


def max(a,b):             #  函数名（参数列表）:
    if a>b:               #  函数体
        return a
    else:
        return b


# 实例：输出 Hello Word！

def hello():
    print('Hello Word!')

hello()

# 比较两个数的大小

def max(a,b):
    if a>b:
        return a 
    else:
        return b

a = 4
b = 5
print(max(a,b))

# 计算面积

def area(height,weight):
    return height * weight

h = 6
w = 8
print('矩形的面积为：',area(h,w))

# x = ('apple', 'banana', 'cherry')
# y = id(x)
# print(y,'\n')    # python 内置的id（）函数    

### 函数调用
# 定义一个函数：给了函数一个名称，指定了函数里包含的参数，和代码块结构。

# 这个函数的基本结构完成以后，你可以通过另一个函数调用执行，也可以直接从 Python 命令提示符执行。

# 如下实例调用了 printme() 函数：

def printme(str):
    print(str)

printme("调用函数")

### 参数传递
# 以下是调用函数时可使用的正式参数类型：

# 必需参数
# 关键字参数
# 默认参数
# 不定长参数

# 在 python 中，类型属于对象，对象有不同类型的区分，变量是没有类型的：

# a=[1,2,3]

# a="Runoob"
# 以上代码中，[1,2,3] 是 List 类型，"Runoob" 是 String 类型，而变量 a 是没有类型，她仅仅是一个对象的引用（一个指针），可以是指向 List 类型对象，也可以是指向 String 类型对象。

# 可更改(mutable)与不可更改(immutable)对象
# 在 python 中，strings, tuples, 和 numbers 是不可更改的对象，而 list,dict 等则是可以修改的对象。

# 不可变类型：变量赋值 a=5 后再赋值 a=10，这里实际是新生成一个 int 值对象 10，再让 a 指向它，而 5 被丢弃，不是改变 a 的值，相当于新生成了 a。

# 可变类型：变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改，本身la没有动，只是其内部的一部分值被修改了。

# python 函数的参数传递：

# 不可变类型：类似 C++ 的值传递，如整数、字符串、元组。如 fun(a)，传递的只是 a 的值，没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值，则是新生成一个 a 的对象。

# 可变类型：类似 C++ 的引用传递，如 列表，字典。如 fun(la)，则是将 la 真正的传过去，修改后 fun 外部的 la 也会受影响

# python 中一切都是对象，严格意义我们不能说值传递还是引用传递，我们应该说传不可变对象和传可变对象。

###  python 传不可变对象实例
# 通过 id() 函数来查看内存地址变化
def adress(a):
    print(id(a))    # 指向同一个对象
    a = 10
    print(id(a))    # 指向一个新的对象

a = 1
print(id(a))
adress(a)
print(id(a))

### python 传可变量实例
# 可变对象在函数里修改了参数，那么在调用这个函数的函数里，原始的参数也被改变了。例如：
def changelist(mylist):
    # mylist.append([1,2,3])        # append() 用于在 列表 末尾添加新的对象 
    mylist = [1,2,3]
    print('函数内取值：',mylist)
    return

mylist = [20,30]
changelist(mylist)
print('函数外的值：',mylist)


### 必需参数
# 必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
# 调用 printme() 函数，你必须传入一个参数，不然会出现语法错误：
def printm(str):
    print("函数被调用了？",str)
    return

strin = '已经调用了这个函数!'
printm(strin)               # 不加参数会报错


### 关键字参数
# 关键字参数和函数调用关系紧密，函数调用使用关键字参数来确定传入的参数值。
# 使用关键字参数允许函数调用时参数的顺序与声明时不一致，因为 Python 解释器能够用参数名匹配参数值。
# 以下实例在函数 printme() 调用时使用参数名：

def printme(str):
    print(str)
    return

printme(str = '关键字参数在这调用！')

### 不定长参数
# 你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数，和上述 2 种参数不同，声明时不会命名。基本语法如下：
# def functioname([formal_args,] * var_args_tuple):
#     # 函数_文档字符串
#     function_suie
#     return [expression]


### 加了星号 * 的参数会以元组(tuple)的形式导入，存放所有未命名的变量参数
def printinfo(age,*name):
    # 打印任何传入的参数
    print('输出')
    print(age)
    print(name)         # name 为加*参数，传参携带未命名参数，以元组的形式输出

printinfo( 20, 30,50,60,7 )

### 如果在函数调用时没有指定参数，它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例：

def printinfo(name ,*newtuple):
    print("输出:")
    print(name)
    print(newtuple)             # 如果调用时，未传参数，此处输出空数组
    for i in newtuple:
        print(i)
    return

printinfo(20,30,40,50,60)
printinfo( 10 )               

### 还有一种就是参数带两个星号 **基本语法如下：
def printinfo(name,**newdict):                      # 加了两个 * 的参数会以字典的形式导入
    print("shuchu:")
    print(name)
    print(newdict)
    for i in newdict:
        print(i)

# printinfo(20)
printinfo(20,a=30,b=40,c=50,d=60)


### 声明函数，可以用 * 当作参数单独传入，但 * 后的参数必须以关键字出现；
def printinfo(a,b,*,c):
    return a+b+c

printinfo(1,2,3)        # 会报错
# 改

def printinfo(a,b,*,c):
    return a+b+c

printinfo(1,2,c=3)          # 以关键字的形式传入


## 总结： 声明函数，参数中带一个 *  号，结果输出未元组（tuple），带两个为 字典（dict）


#### 匿名函数
# Python 用 lambda 来创建匿名函数 

# 所谓匿名，意即不再使用 def 语句这样标准的形式定义一个函数。

# lambda 只是一个表达式，函数体比 def 简单很多。
# lambda 的主体是一个表达式，而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
# lambda 函数拥有自己的命名空间，且不能访问自己参数列表之外或全局命名空间里的参数。
# 虽然 lambda 函数看起来只能写一行，却不等同于 C 或 C++ 的内联函数，后者的目的是调用小函数时不占用栈内存从而增加运行效率。

# 实例
x = lambda a:a+4
print(x(5))       # 输出结果为 9 

# 以下实例匿名函数设置两个参数：

# 可写函数说明(年龄函数)
sum = lambda age1,age2: age1+age2

print(sum(10,15))


# 我们可以将匿名函数封装在一个函数内，这样可以使用同样的代码来创建多个匿名函数。
# 以下实例将匿名函数封装在 myfunc 函数中，通过传入不同的参数来创建不同的匿名函数：

def myfunction(n):
    return lambda a:a*n

mydou = myfunction(2)

print(mydou(10))            # 输出的结果为 20


### return 语句
# return [表达式] 语句用于退出函数，选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。之前的例子都没有示范如何返回数值，以下实例演示了 return 语句的用法

#！usr/bin/env python
def sum(var1,var2):
    total = var1 + var2
    print("函数内：", total)
    return total            # 若是不写 return or return 后面不加total表达式，函数外都会输出 None

a = sum(10,15)
print("函数外：",a)